#ifndef YCBASEREQUEST_H
#define YCBASEREQUEST_H
#include "base/basetype.h"
#include <functional>
#include <memory>
#include <QDateTime>
#include <QHttpMultiPart>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QMap>
#include <QNetworkReply>
#include <QPointer>
#include <QString>
#include <QtDebug>

namespace yc {

using RequestHeaders = QMap<QString, QString>;

class Request
{
public:
    enum Method { POST_METHOD, GET_METHOD, PUT_METHOD, DELETE_METHOD, HEAD_METHOD };

    Request(int seq, const QString& url, const QString& data, Method method = POST_METHOD,
            const RequestHeaders& headers = RequestHeaders(),
            const QString& content_type = "application/json;charset=UTF-8")
        : seq_(seq),
          method_(method),
          headers_(headers),
          url_(url),
          data_(data),
          content_type_(content_type)
    {
    }

    int seq() const { return seq_; }
    Method method() const { return method_; }
    const RequestHeaders& headers() const { return headers_; }
    const QString& url() const { return url_; }
    const QString& data() const { return data_; }
    const QString& contentType() const { return content_type_; }
    const QVariant& userData() const { return user_data_; }
    void setUserData(const QVariant& user_data) { user_data_ = user_data; }
    std::shared_ptr<QHttpMultiPart> multiPart() const { return multi_part_; }
    void setMultiPart(const std::shared_ptr<QHttpMultiPart>& multi_part)
    {
        multi_part_ = std::shared_ptr<QHttpMultiPart>(multi_part);
    }

private:
    int seq_;
    Method method_;
    RequestHeaders headers_;
    QString url_;
    QString data_;
    QString content_type_;
    QVariant user_data_;
    std::shared_ptr<QHttpMultiPart> multi_part_;
};

class Response
{
public:
    explicit Response(const QByteArray& data, int status = OK_CODE, const QString& msg = QString(),
                      const QDateTime& last_modified = QDateTime())
        : raw_data_(data), status_(status), msg_(msg), last_modified_(last_modified)
    {
        QJsonDocument doc = QJsonDocument::fromJson(data);
        if (doc.isObject()) {
            QJsonObject jo = doc.object();
            if (!jo.isEmpty()) {
                if (status == OK_CODE) {
                    status_ = jo["status"].toInt();
                }
                QString msg = jo["msg"].toString();
                if (!msg.isEmpty()) {
                    msg_ = msg;
                }
                data_ = jo["data"];
            }
        } else if (doc.isArray()) {
            data_ = doc.array();
        }
    }
    Response(int status, const QString& msg) : status_(status), msg_(msg) { }

    const QByteArray& rawData() const { return raw_data_; }
    int status() const { return status_; }
    const QString& msg() const { return msg_; }
    const QJsonValue& data() const { return data_; }
    const QDateTime& lastModified() const { return last_modified_; }

private:
    QByteArray raw_data_;
    int status_;
    QString msg_;
    QJsonValue data_;
    QDateTime last_modified_;
};

using ResponseFunctor = std::function<void(const Request&, const Response&)>;

template<class T>
struct ResponseFunctionTrait
{
    using F = void (T::*)(const Request&, const Response&);
};

class ResponseHandler
{
public:
    ResponseHandler() { }

    template<class T>
    ResponseHandler(T* obj, typename ResponseFunctionTrait<T>::F f)
        : is_qo_f_(true), obj_(obj), obj_used_(obj != nullptr)
    {
        f_ = std::bind(f, obj, std::placeholders::_1, std::placeholders::_2);
    }
    template<class T>
    explicit ResponseHandler(T* obj, const ResponseFunctor& f)
        : is_qo_f_(false), obj_(obj), obj_used_(obj != nullptr), f_(f)
    {
    }
    explicit ResponseHandler(const ResponseFunctor& f)
        : is_qo_f_(false), obj_used_(false), f_(f) { }

    QObject* receiver() const { return obj_.data(); }

    void operator()(const Request& request, const Response& response) const
    {
        if (obj_used_ && obj_.isNull())
            return;
        if ((!is_qo_f_ && f_) || (is_qo_f_ && obj_)) {
            f_(request, response);
        }
    }

private:
    bool is_qo_f_;
    QPointer<QObject> obj_;
    bool obj_used_;
    ResponseFunctor f_;
};

} // namespace yc

#endif // YCBASEREQUEST_H
